/*
    AppMixerThread.cpp
    2006 John Wiggins

// This program is free software; you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation; either version 2 of the License, or     
// ( at your option ) any later version.                                   
// 
// This program is distributed in the hope that it will be useful,       
// but WITHOUT ANY WARRANTY; without even the implied warranty of        
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
// GNU General Public License for more details.                          
//     
//     You should have received a copy of the GNU General Public License     
//     along with this program; if not, write to the                         
//         Free Software Foundation, Inc.,                                       
//         59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.  
*/
//-------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <pthread.h>
#include <assert.h>

#include "App/AppMixerThread.h"
#include "App/AppMessageConstants.h"
//-------------------------------------------------------------
using std::map;
using std::vector;
using std::pair;
//-------------------------------------------------------------
AppMixerThread::AppMixerThread()
{
    // spawn our work thread
    if ( 0 != pthread_create( &mThread, NULL, start_loop, ( void * )this ) )
    {
        printf( "error creating the mixer thread\n" );
        exit( -1 );
    }

    // create our message port
    mMsgPort = pp_create_port( "mixer thread" );
    // check for error
    if ( mMsgPort < GE_NO_ERROR )
    {
        printf( "Error creating the mixer thread's port. ( %d )\n", mMsgPort );
        exit( -1 );
    }
    
    // we aren't qutting right now
    mQuitting = false;
    // we have no output yet
    mOutPlugin = NULL;
}
//-------------------------------------------------------------
AppMixerThread::~AppMixerThread()
{
    // send a quit message to the thread
    IpcMessage quitmsg( GE_QUIT );
    quitmsg.SendMessage( mMsgPort );

    // wait for the thread to exit
    pthread_join( mThread, NULL );

    // delete the message port
    pp_delete_port( mMsgPort );

    // delete the mixers
    map<ge_mixer_plugin *, AppMixer *>::iterator i;
    for ( i = mMixerMap.begin(); i != mMixerMap.end(); ++i )
    {
        // delete the AppMixer instance
        delete ( *i ).second;
    }
}
//-------------------------------------------------------------
void
AppMixerThread::receiveMessages( bool waitForMessages )
{
    int32 size, ret;
    uint32 code = 0;
    char *buffer = NULL;

    // read messages as long as there are messages to be read
    while ( pp_port_has_message( mMsgPort ) >= GE_NO_ERROR || waitForMessages )
    {
        if ( ( size = pp_port_buffer_size( mMsgPort ) ) >= GE_NO_ERROR )
        {
            // allocate space for the message
            char *p = ( char * )realloc( buffer, size );
            if ( p == NULL )
            {
                printf( "realloc(  ) failure! %d : %s\n", __LINE__, __FILE__ );
                break;
            }
        
            buffer = p;
            // retrieve the message
            ret = pp_read_port( mMsgPort, &code, ( void * )buffer, size );

            // do something with the message
            if ( ret >= GE_NO_ERROR )
            {
                IpcMessage message( code );
                ret = message.Unflatten( buffer );
                if ( ret >= GE_NO_ERROR )
                    handleMessage( &message );
            }

            // check for quit
            if ( mQuitting )
                break;
            
            // we've received at least one message
            waitForMessages = false;
        }
        else
            break;
    }

    // free the message buffer
    free( buffer );
}
//-------------------------------------------------------------
void
AppMixerThread::handleMessage( IpcMessage *msg )
{
    switch ( msg->what )
    {
        case GE_QUIT:
        {
            mQuitting = true;
            break;
        }
        case GE_ADD_FILTER:
        {
            // message contains:
            // - "filter" : BAS_POINTER_TYPE : a pointer to the new filter
            // - "mixer" : BAS_POINTER_TYPE : a pointer to the controlling mixer
            // - "successor" : BAS_POINTER_TYPE : a pointer to the filter that will run
            //      after this filter. ( may be NULL )
            ge_filter_plugin *filter = NULL, *successor = NULL;
            ge_mixer_plugin *mixer = NULL;

            // fish the pointers out of the message
            msg->FindPointer( "filter", ( void const ** )&filter );
            msg->FindPointer( "mixer", ( void const ** )&mixer );
            msg->FindPointer( "successor", ( void const ** )&successor );

            // add the filter
            addFilter( mixer, filter, successor );

            break;
        }
        case GE_REMOVE_FILTER:
        {
            // message contains:
            // - "filter" : BAS_POINTER_TYPE : a pointer to the plugin instance
            // - "mixer" : BAS_POINTER_TYPE : a pointer to the controlling mixer
            // - "reply" : BAS_INT32_TYPE : a reply port number
            // - "destroy" : BAS_BOOL_TYPE : a flag indicating whether another process
            //      is waiting for a reply so that the plugin instance can be destroyed
            ipc_port_t replyPort;
            ge_filter_plugin *filter;
            ge_mixer_plugin *mixer;
            bool destroy;

            // grab the data
            msg->FindPointer( "filter", ( void const ** )&filter );
            msg->FindPointer( "mixer", ( void const ** )&mixer );
            msg->FindInt32( "reply", ( ipc_port_t * )&replyPort );
            msg->FindBool( "destroy", &destroy );

            // remove the filter
            removeFilter( mixer, filter );

            // maybe this filter is going away for good?
            if ( destroy )
            {
                // change the message to a GE_DESTROY_FILTER message
                msg->what = GE_DESTROY_FILTER;
                msg->RemoveName( "reply" );
                // send the reply
                msg->SendMessage( replyPort );
            }

            break;
        }
        case GE_ADD_MIXER:
        {
            // message contains:
            // - "mixer" : BAS_POINTER_TYPE : a pointer to the mixer instance
            ge_mixer_plugin *mixer;

            msg->FindPointer( "mixer", ( void const ** )&mixer );

            // add the mixer
            addMixer( mixer );
            
            break;
        }
        case GE_REMOVE_MIXER:
        {
            // message contains:
            // - "mixer" : BAS_POINTER_TYPE : a pointer to the mixer instance
            // - "reply" : BAS_INT32_TYPE : a reply port number
            // - "destroy" : BAS_BOOL_TYPE : a flag indicating whether another process
            //      is waiting for a reply so that the plugin instance can be destroyed
            ge_mixer_plugin *mixer;
            ipc_port_t replyPort;
            bool destroy = false;

            msg->FindPointer( "mixer", ( void const ** )&mixer );
            msg->FindInt32( "reply", ( ipc_port_t * )&replyPort );
            msg->FindBool( "destroy", &destroy );

            // remove the mixer
            removeMixer( mixer );

            // maybe this mixer is going away for good?
            if ( destroy )
            {
                // change the message to a GE_DESTROY_MIXER message
                msg->what = GE_DESTROY_MIXER;
                msg->RemoveName( "reply" );
                // send the reply
                msg->SendMessage( replyPort );
            }

            break;
        }
        case GE_CONNECT_OUTPUT:
        {
            // message contains:
            // - "ouput" : BAS_POINTER_TYPE : a pointer to the ouput instance
            ge_output_plugin *output;
            
            msg->FindPointer( "output", ( void const ** )&output );

            // connect to the output
            connectOutput( output );
            
            break;
        }
        case GE_DISCONNECT_OUTPUT:
        {
            // message contains:
            // - "ouput" : BAS_POINTER_TYPE : a pointer to the ouput instance
            // - "reply" : BAS_INT32_TYPE : a reply port number
            // - "destroy" : BAS_BOOL_TYPE : a flag indicating whether another process
            //      is waiting for a reply so that the plugin instance can be destroyed
            ge_output_plugin *output;
            ipc_port_t replyPort;
            bool destroy = false;
            
            msg->FindPointer( "output", ( void const ** )&output );
            msg->FindInt32( "reply", ( ipc_port_t * )&replyPort );
            msg->FindBool( "destroy", &destroy );

            // disconnect from the output
            disconnectOutput( output );
            
            // maybe this output is going away for good?
            if ( destroy )
            {
                // change the message to a GE_DESTROY_OUTPUT message
                msg->what = GE_DESTROY_OUTPUT;
                msg->RemoveName( "reply" );
                // send the reply
                msg->SendMessage( replyPort );
            }

            break;
        }
        case GE_CONNECT_SOURCE:
        {
            // message contains:
            // - "source" : BAS_POINTER_TYPE : a pointer to a AppSourceThread
            // - "mixer" : BAS_POINTER_TYPE : a pointer to a mixer instance
            AppSourceThread *source;
            ge_mixer_plugin *mixer;
            
            msg->FindPointer( "source", ( void const ** )&source );
            msg->FindPointer( "mixer", ( void const ** )&mixer );

            // connect to the source thread
            connectSource( mixer, source );

            break;
        }
        case GE_DISCONNECT_SOURCE:
        {
            // message contains:
            // - "source" : BAS_POINTER_TYPE : a pointer to a AppSourceThread
            // - "mixer" : BAS_POINTER_TYPE : a pointer to a mixer instance
            // - "reply" : BAS_INT32_TYPE : a reply port number
            // - "destroy" : BAS_BOOL_TYPE : a flag indicating whether another process
            //      is waiting for a reply so that the plugin instance can be destroyed
            AppSourceThread *source;
            ge_mixer_plugin *mixer;
            ipc_port_t replyPort;
            bool destroy = false;
            
            msg->FindPointer( "source", ( void const ** )&source );
            msg->FindPointer( "mixer", ( void const ** )&mixer );
            msg->FindInt32( "reply", ( ipc_port_t * )&replyPort );
            msg->FindBool( "destroy", &destroy );

            // disconnect from the source thread
            disconnectSource( mixer, source );

            // maybe this source is going away for good?
            if ( destroy )
            {
                // change the message to a GE_DESTROY_SOURCE message
                msg->what = GE_DESTROY_SOURCE;
                msg->RemoveName( "reply" );
                // send the reply
                msg->SendMessage( replyPort );
            }

            break;
        }
        case GE_MIXER_ORDERING:
        {
            // message contains:
            // - "mixers" : BAS_POINTER_TYPE : a list of pointers to mixer instances
            //  ( the mixers are stored in the order they should run )
            // - "0" : BAS_INT32_TYPE : a list of indices into the "mixers" list
            // - ... : BAS_INT32_TYPE : a list of indices into the "mixers" list
            // - "n" : BAS_INT32_TYPE : a list of indices into the "mixers" list

            // swap out the mixer ordering
            updateMixerOrder( msg );
            
            break;
        }
        default:
            printf( "unhandled message in AppMixerThread::handleMessage!"
                            " ( what = %d )\n", msg->what );
            break;
    }
}
//-------------------------------------------------------------
void *
AppMixerThread::start_loop( void *data )
{
    return ( ( AppMixerThread * )data )->main_loop();
}
//-------------------------------------------------------------
void *
AppMixerThread::main_loop()
{
    while ( true )
    {
        // receive any messages sent to the mixer
        // the messages contain: mixer plugins, filters, quit requests
        receiveMessages( ( mOutPlugin == NULL ) || mActiveMixList.empty() );

        // main graphics kernel
        if ( mOutPlugin != NULL && !mActiveMixList.empty() )
        {
            // get a pointer to the last mixer in the chain
            AppMixer *lastMixer = mActiveMixList[mActiveMixList.size()-1].first;

            // run the mixers
            mix();

            // output the result
            mOutPlugin->output( mOutPlugin, lastMixer->getSurface() );
        }

        // quit if we need to
        if ( mQuitting ) break;
    }
    
    return NULL;
}
//-------------------------------------------------------------
void
AppMixerThread::addFilter( ge_mixer_plugin *mixer, ge_filter_plugin *filter,
                          ge_filter_plugin *before )
{
    // make sure we have this mixer
    assert( mMixerMap.count( mixer ) != 0 );

    // add the filter to the mixer
    mMixerMap[mixer]->addFilter( filter, before );
}
//-------------------------------------------------------------
void
AppMixerThread::removeFilter( ge_mixer_plugin *mixer,
                             ge_filter_plugin *filter )
{
    // make sure we have this mixer
    assert( mMixerMap.count( mixer ) != 0 );

    // remove the filter from the mixer
    mMixerMap[mixer]->removeFilter( filter );
}
//-------------------------------------------------------------
void
AppMixerThread::addMixer( ge_mixer_plugin *mixer )
{
    // make sure this mixer isn't already resident
    assert( mMixerMap.count( mixer ) == 0 );

    // add the mixer
    mMixerMap[mixer] = new AppMixer( mixer );
}
//-------------------------------------------------------------
void
AppMixerThread::removeMixer( ge_mixer_plugin *mixer )
{
    // make sure we have this mixer
    assert( mMixerMap.count( mixer ) != 0 );

    AppMixer *mix = mMixerMap[mixer];
    bool active = false;
    
    // see if the mixer being removed is active or not
    vector<pair<AppMixer*,vector<int32> > >::iterator i = mActiveMixList.begin();
    for ( ; i != mActiveMixList.end(); ++i )
    {
        if ( ( *i ).first == mix )
        {
            // ACK! an active mixer is being removed!
            // you know what? The whole list can be flushed and when the
            // user repairs the damage, a new list will be loaded anyways
            active = true;
            break;
        }
    }

    // is it active?
    if ( active )
    {
        // uh oh... Go ahead and flush the list
        mActiveMixList.clear();
    }

    // remove any sources connected to this mixer
    mSourceMap.erase( mix );
    
    // remove from the map and delete
    mMixerMap.erase( mixer );
    delete mix;
}
//-------------------------------------------------------------
void
AppMixerThread::connectOutput( ge_output_plugin *output )
{
    // make sure we don't have an output assigned yet
    assert( mOutPlugin == NULL );

    // assign the output
    mOutPlugin = output;
}
//-------------------------------------------------------------
void
AppMixerThread::disconnectOutput( ge_output_plugin *output )
{
    // make sure the correct output is being disconnected
    assert( output == mOutPlugin );

    // un-assign the output
    mOutPlugin = NULL;
}
//-------------------------------------------------------------
void
AppMixerThread::connectSource( ge_mixer_plugin *mixer,
                AppSourceThread *source )
{
    //std::map<ge_mixer_plugin*,AppMixer*> mMixerMap;
    //std::map<AppMixer*,vector<AppSourceThread*> > mSourceMap;

    // make sure the mixer is there
    assert( mMixerMap.count( mixer ) != 0 );

    // if there aren't any sources for this mixer, construct the vector
    if ( mSourceMap.count( mMixerMap[mixer] ) == 0 )
        mSourceMap[mMixerMap[mixer]] = vector<AppSourceThread*>();
    
    // add the source
    mSourceMap[mMixerMap[mixer]].push_back( source );
}
//-------------------------------------------------------------
void
AppMixerThread::disconnectSource( ge_mixer_plugin *mixer,
                AppSourceThread *source )
{
    // make sure the mixer is there
    assert( mMixerMap.count( mixer ) != 0 );

    // make sure the source list is there
    assert( mSourceMap.count( mMixerMap[mixer] ) != 0 );

    // loop through all the sources for this mixer
    vector<AppSourceThread*>::iterator i = mSourceMap[mMixerMap[mixer]].begin();
    for ( ; i != mSourceMap[mMixerMap[mixer]].end(); ++i )
    {
        if ( *i == source )
        {
            // remove the source
            mSourceMap[mMixerMap[mixer]].erase( i );
            // that's it
            break;
        }
    }
}
//-------------------------------------------------------------
void
AppMixerThread::updateMixerOrder( IpcMessage *msg )
{
    // message contains:
    // - "mixers" : BAS_POINTER_TYPE : a list of pointers to mixer instances
    //  ( the mixers are stored in the order they should run )
    // - "0" : BAS_INT32_TYPE : a list of indices into the "mixers" list
    // - ... : BAS_INT32_TYPE : a list of indices into the "mixers" list
    // - "n" : BAS_INT32_TYPE : a list of indices into the "mixers" list
    //std::vector<pair<AppMixer *,vector<int32> > > mActiveMixList;
    int32 index=0;
    status_t ret = GE_NO_ERROR;

    // clear out the previous ordering
    mActiveMixList.clear();

    // grab the mixers out of the message and put them in the active list
    while ( ret == GE_NO_ERROR )
    {
        ge_mixer_plugin *mixer;
        
        // grab the pointer at msg["mixer"][index]
        ret = msg->FindPointer( "mixer", index, ( void const ** )&mixer );

        if ( ret == GE_NO_ERROR )
        {
            // don't add mixers that aren't there
            assert( mMixerMap.count( mixer ) != 0 );
            mActiveMixList.push_back( pair<AppMixer*,vector<int32> >( mMixerMap[mixer],vector<int32>() ) );
        
            // increment the index
            index++;
        }
    }

    // grab the indices out of the list
    for ( index = 0; index < mActiveMixList.size(); index++ )
    {
        char num[3];
        int32 grab, item_index=0;

        // which index are we grabbing numbers for?
        snprintf( num, sizeof( num ), "%d", index );
        // grab the first one ( or none at all? )
        ret = msg->FindInt32( num, item_index, &grab );
        // grab the rest
        while ( ret != GE_NO_ERROR )
        {
            // add the number to the list
            mActiveMixList[index].second.push_back( grab );
            // grab another one
            item_index++;
            ret = msg->FindInt32( num, item_index, &grab );
        }
    }
}
//-------------------------------------------------------------
void
AppMixerThread::mix()
{
    RenSurface *images[64];
    uint32 index;
    uint32 count;
    
    // go through the active mixers
    for( index = 0; index < mActiveMixList.size(); index++ )
    {
        AppMixer *mixer = mActiveMixList[index].first;
        vector<int32>::iterator ins = mActiveMixList[index].second.begin();

        // reset the count
        count = 0;

        // build the input list of source images for this mixer
        // first the mixer outputs
        for ( ; ins != mActiveMixList[index].second.end(); ++ins )
            images[count++] = mActiveMixList[*ins].first->getSurface();
        // then the sources
        if ( mSourceMap.count( mixer ) != 0 )
        {
            vector<AppSourceThread*>::iterator srcs = mSourceMap[mixer].begin();
            for ( ; srcs != mSourceMap[mixer].end(); ++srcs )
            {
                // lock the source's output
                if ( ( *srcs )->lockSurface() )
                    images[count++] = ( *srcs )->getSurface();
                else
                    printf( "Ack! Failed to lock a source's output!!!\n" );
            }
        }

        // run the mixer
        if ( count > 0 )
            mixer->mix( count, images );

        // unlock any source images that were used
        if ( mSourceMap.count( mixer ) != 0 )
        {
            vector<AppSourceThread*>::iterator srcs = mSourceMap[mixer].begin();
            for ( ; srcs != mSourceMap[mixer].end(); ++srcs )
                ( *srcs )->unlockSurface();
        }
    }
}
//-------------------------------------------------------------
